import java.net.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;

//implements reliable broadcast
public class ReliableChannel extends BroadcastChannel{
    ChatClientRecord owner;//the owner of this channel (the one who sends on it)
    ApplicationListener listener;//the application who wants to receive the messages
    ArrayList<ChatClientRecord> group;//list of members of this channel
    MessageReceiver receiver;//listens for incoming messages
    long messageCounter;//creates the unique message ID for messages originating on this channel

    //Initialize a reliable broadcast channel with the initial member with record 'rec'
    public ReliableChannel(ChatClientRecord rec, ApplicationListener l){
        owner = rec;//initialize the channel owner (sender)
        listener = l;//initialze the application listener
        messageCounter = 0;//initialize the unique message ID counter to 0

        //initialize the chat group
        group = new ArrayList<ChatClientRecord>();
        group.add(rec);

        //start listening for incoming messages
        receiver = new MessageReceiver(rec.port);
        receiver.start();
    }

    //add a member to the channel
    //Returns true if successful
    public boolean addMember(ChatClientRecord member){
        if(!group.contains(member)){
            return group.add(member);
        }
        return false;
    }

    //remove a member from the channel
    //Returns true if successful
    public boolean removeMember(ChatClientRecord member){
        return group.remove(member);
    }

    //Return the current list of members
    public ArrayList<ChatClientRecord> getMembers(){
        return group;
    }

    //broadcast a message
    //Return true if successful
    public boolean sendMessage(ChatMessage m){
        //set the unique message identifier only if it hasn't been set
        //(this means we don't give forwarded messages new identifiers
        if(m.messID == -1){
            m.messID = messageCounter;
            messageCounter++;
        }
        //send the message to everyone in the group including yourself
        for(int i = 0; i < group.size(); i++){
            ChatClientRecord rec = group.get(i);
            new MessageSender(rec.ip, rec.port, m).start();
        }

        //the message was sent to yourself, so it'll be 'received' by the client then

        return true;
    }

    private class MessageSender extends Thread {
        private String ip;
        private long port;
        ChatMessage m;

        public MessageSender(String ip, long port, ChatMessage m){
            this.ip = ip;
            this.port = port;
            this.m = m;
        }

        public void run(){
            try{
                //open connection
                Socket sock = new Socket(ip, (int)port);    
                ObjectOutputStream out = new ObjectOutputStream(sock.getOutputStream());
                //write message
                out.writeObject(m);
                //close connection
                sock.close();
                out.close();
            } catch (UnknownHostException e) {
                System.err.println("Could not connect to " + m.clientRec.name + " at " + ip + ":" + port);
            } catch (IOException e) {
                System.err.println("Error: " + e.getMessage() + " to " + ip + ":" + port);
            }
        }
    }

    private class MessageReceiver extends Thread {
        private ServerSocket sock;
        private HashMap<String, ArrayList<ChatMessage>> totalHistory;

        public MessageReceiver(long port){
            totalHistory = new HashMap<String, ArrayList<ChatMessage>>();
            try{
                sock = new ServerSocket((int)port);
            }catch(IOException e){
                System.err.println("MessageReceiver Error: "+e.getMessage());
                System.exit(1);
            }
        }

        public void run(){
            while(true){
                try{
                    //open connection
                    Socket client = sock.accept();
                    ObjectInputStream in = new ObjectInputStream(client.getInputStream());
                    //System.out.println("Accepted connection from " +client.getInetAddress().getHostName() + ":" + client.getPort());

                    //read message
                    ChatMessage message = (ChatMessage)in.readObject();

                    //check to see if we've seen the message before or not
                    synchronized (totalHistory){
                        //get our local copy of the message sender's history
                        ArrayList<ChatMessage> clientHistory = totalHistory.get(message.clientRec.name);

                        //if we don't have a history for them yet
                        if(clientHistory == null){
                            //add a history
                            clientHistory = new ArrayList<ChatMessage>();
                            //add the message to the history
                            clientHistory.add(message);
                            totalHistory.put(message.clientRec.name, clientHistory);

                            //send it to everyone you know
                            sendMessage(message);

                            //deliver message to self
                            listener.receive(message);
                        }else{
                            //if we haven't already seen this message
                            if(!clientHistory.contains(message)){
                                //add the message to the history
                                clientHistory.add(message);

                                //send it to everyone you know
                                sendMessage(message);

                                //deliver message to self
                                listener.receive(message);
                            }//else ignore the message
                        }
                    }

                    //close connection
                    in.close();
                    client.close();
                }catch(IOException e){
                    System.err.println("Error: "+e.getMessage());
                }catch(ClassNotFoundException e){
                    System.err.println("Error: "+e.getMessage());
                    System.exit(1);//if we can't find the class then we can't continue in the client
                }
            }
        }
    }
}
